#include <stdio.h>
#include <time.h>
#include <libavcodec/avcodec.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
//test different codec

typedef void(*VideoCallback)(unsigned char *buff, int size, double timestamp);
typedef void(*AudioCallback)(unsigned char *buff, int size, double timestamp);
typedef void(*RequestCallback)(int offset, int available);
#ifdef __cplusplus
extern "C" {
#endif

#define ENABLE_PARSER 0
#define CHUNKSIZE 655350
#define MIN(X, Y)  ((X) < (Y) ? (X) : (Y))

typedef enum ErrorCode {
    kErrorCode_Success = 0,
    kErrorCode_Invalid_Param,
    kErrorCode_Invalid_State,
    kErrorCode_Invalid_Data,
    kErrorCode_Invalid_Format,
    kErrorCode_NULL_Pointer,
    kErrorCode_Open_File_Error,
    kErrorCode_Eof,
    kErrorCode_FFmpeg_Error,
    kErrorCode_Old_Frame
} ErrorCode;

typedef struct WebDecoder {
	AVCodec *pCodec;
	AVCodecContext *pCodecCtx;
#if ENABLE_PARSER    
	AVCodecParserContext *pCodecParserCtx;
#endif    
	enum AVCodecID codec_id;
	AVFrame	*pFrame;
    VideoCallback videoCallback;

    FILE *fp;
    char fileName[64];
    int64_t fileSize;
    int64_t fileReadPos;
    int64_t fileWritePos;	

    int videoBufferSize;
    int videoSize;	
	unsigned char *yuvBuffer;
    int width;
    int height;
} WebDecoder;

int first_time=1;
FILE *fp_in;
FILE *fp_out;

uint8_t* ready_to_decode;
int ready_to_decode_size;

WebDecoder *decoder = NULL;

unsigned long get_file_size(const char *path)  
{  
    unsigned long filesize = -1;      
    struct stat statbuff;  
    if(stat(path, &statbuff) < 0){  
        return filesize;  
    }else{  
        filesize = statbuff.st_size;  
    }  
    return filesize;  
}

unsigned long getTickCount() {
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    return ts.tv_sec * (unsigned long)1000 + ts.tv_nsec / 1000000;
}

int getAailableDataSize() {
    int ret = 0;
    do {
        if (decoder == NULL) {
            break;
        }
        ret = decoder->fileWritePos - decoder->fileReadPos;
    } while (0);
    return ret;
}

int writeToFile(unsigned char *buff, int size) {
    int ret = 0;
    int64_t leftBytes = 0;
    int canWriteBytes = 0;
    do {
        if (decoder->fp == NULL) {
            ret = -1;
            break;
        }

        leftBytes = decoder->fileSize - decoder->fileWritePos;
        if (leftBytes <= 0) {
            break;
        }

        canWriteBytes = MIN(leftBytes, size);
        fseek(decoder->fp, decoder->fileWritePos, SEEK_SET);
        fwrite(buff, canWriteBytes, 1, decoder->fp);
        decoder->fileWritePos += canWriteBytes;
        ret = canWriteBytes;
    } while (0);
    return ret;
}


int readFromFile(uint8_t *data, int len) {
    //simpleLog("readFromFile %d.", len);
    int32_t ret         = -1;
    int availableBytes  = 0;
    int canReadLen      = 0;
    do {
        if (decoder->fp == NULL) {
            break;
        }

        availableBytes = decoder->fileWritePos - decoder->fileReadPos;
        if (availableBytes <= 0) {
            break;
        }

        fseek(decoder->fp, decoder->fileReadPos, SEEK_SET);
        canReadLen = MIN(availableBytes, len);
        fread(data, canReadLen, 1, decoder->fp);
        decoder->fileReadPos += canReadLen;
        ret = canReadLen;
    } while (0);
    //simpleLog("readFromFile ret %d.", ret);
    return ret;
}

ErrorCode copyYuvData(AVFrame *frame, uint8_t *buffer, int width, int height) {
    ErrorCode ret		= kErrorCode_Success;
    uint8_t *src	= NULL;
    uint8_t *dst	= buffer;
    int i = 0;
    do {
        if (frame == NULL || buffer == NULL) {
            ret = kErrorCode_Invalid_Param;
            break;
        }

        if (!frame->data[0] || !frame->data[1] || !frame->data[2]) {
            ret = kErrorCode_Invalid_Param;
            break;
        }

        for (i = 0; i < height; i++) {
            src = frame->data[0] + i * frame->linesize[0];
            memcpy(dst, src, width);
            dst += width;
        }

        for (i = 0; i < height / 2; i++) {
            src = frame->data[1] + i * frame->linesize[1];
            memcpy(dst, src, width / 2);
            dst += width / 2;
        }

        for (i = 0; i < height / 2; i++) {
            src = frame->data[2] + i * frame->linesize[2];
            memcpy(dst, src, width / 2);
            dst += width / 2;
        }
    } while (0);
    return ret;	
}

int frameToYuv(uint8_t* frame, int size) {
    uint8_t* buffer = frame;
    int buffer_size = size;
	int ret = 0;
    int got_picture;

#if 1
    int nal_type = frame[4] & 0x1f;
    if(nal_type != 1 && nal_type != 5) {
        if(ready_to_decode == NULL) {
            ready_to_decode = frame;
        }
        ready_to_decode_size += size;
        return 0;
    } else {
        if(ready_to_decode) {
            buffer = ready_to_decode;
            buffer_size = ready_to_decode_size + size;
        }
        ready_to_decode = NULL;
        ready_to_decode_size = 0;
    }
#endif
	AVPacket packet;
	av_init_packet(&packet);
    packet.data = buffer; 
    packet.size = buffer_size;

    printf("%s|%d||size %d\n", __FUNCTION__, __LINE__, packet.size);
    //Some Info from AVCodecParserContext

    ret = avcodec_decode_video2(decoder->pCodecCtx, decoder->pFrame, &got_picture, &packet);
    if (ret < 0) {
        printf("Decode Error. %d\n", ret);
        av_packet_unref(&packet);
        return -1;
    }
    if (got_picture) {
        if(first_time){
            decoder->videoSize = avpicture_get_size(AV_PIX_FMT_YUV420P, decoder->pCodecCtx->width, decoder->pCodecCtx->height);
            decoder->videoBufferSize = decoder->videoSize;
            decoder->yuvBuffer = (unsigned char *)av_mallocz(decoder->videoBufferSize);
            decoder->width = decoder->pCodecCtx->width;
            decoder->height = decoder->pCodecCtx->height;
            printf("\nCodec Full Name:%s\n",decoder->pCodecCtx->codec->long_name);
            printf("width:%d\nheight:%d\n\n",decoder->width, decoder->height);
            printf("videoSize %d videoBufferSize %d\n", decoder->videoSize, decoder->videoBufferSize);
            first_time=0;
        }

        copyYuvData(decoder->pFrame, decoder->yuvBuffer, decoder->pCodecCtx->width, decoder->pCodecCtx->height);
        // fwrite(decoder->yuvBuffer, 1, decoder->videoSize, fp_out);
        printf("%s|%d||videoCallback %d\n", __FUNCTION__, __LINE__, decoder->videoSize);
        decoder->videoCallback(decoder->yuvBuffer, decoder->videoSize, 0);
    }
	
	av_packet_unref(&packet);
    return 0;
}

#if 1
int decodeBuf(uint8_t* buffer, int buffer_size) {
    int i = 0;
    int length = buffer_size;
    int value = 0;
    int state = 0;
    int result;
    int lastIndex = 0;
    int nalpos = 0;
    int nalsize = 0;
    int naltype;
    while (i < length) {
        value = buffer[i++];
        // finding 3 or 4-byte start codes (00 00 01 OR 00 00 00 01)
        switch (state) {
            case 0:
                if (value == 0) {
                    state = 1;
                }
                break;
            case 1:
                if (value == 0) {
                    state = 2;
                } else {
                    state = 0;
                }
                break;
            case 2:
            case 3:
                if (value == 0) {
                    state = 3;
                } else if (value == 1 && i < length) {
                    if (lastIndex) {
                        // console.log('lastIndex:' + lastIndex + ' i:' + i + ' state:' + state);
                        // result.push(buffer.subarray(lastIndex - 4, i - state -1)); //4 - 4, 34 - 3 - 1
                        nalpos = lastIndex - 4;
                        nalsize = (i - state -1) - (lastIndex - 4);
                        printf("%s|%d||pos %d size %d\n", __FUNCTION__, __LINE__, nalpos, nalsize);
                        frameToYuv(buffer + nalpos, nalsize);
                    }
                    lastIndex = i;
                    state = 0;
                } else {
                    state = 0;
                }
                break;
            default:
                break;
        }
    }

    if (lastIndex) {
        // result.push(buffer.subarray(lastIndex, length));
        if(lastIndex < 4) {
            frameToYuv(buffer, length);
        } else {
            nalpos = lastIndex - 4;
            nalsize = length - (lastIndex - 4);   
            printf("%s|%d||pos %d size %d\n", __FUNCTION__, __LINE__, nalpos, nalsize);     
            frameToYuv(buffer + nalpos, nalsize);
        }
    }
    return 0;
}

#else

int decodeBuf(uint8_t* buf, int bufsize) {
	int cur_size = bufsize;
	uint8_t* cur_ptr = buf;
	int ret = 0;  
    uint8_t* packet_buf;
    int packet_size;
	while (cur_size > 0){
		int len = av_parser_parse2(
			decoder->pCodecParserCtx, decoder->pCodecCtx,
			&packet_buf, &packet_size,
			cur_ptr , cur_size ,
			AV_NOPTS_VALUE, AV_NOPTS_VALUE, AV_NOPTS_VALUE);

		cur_ptr += len;
		cur_size -= len;

		if(packet_size==0)
			continue;

		//Some Info from AVCodecParserContext
		printf("[Packet]Size:%6d\t",packet_size);
		switch(decoder->pCodecParserCtx->pict_type){
			case AV_PICTURE_TYPE_I: printf("Type:I\t");break;
			case AV_PICTURE_TYPE_P: printf("Type:P\t");break;
			case AV_PICTURE_TYPE_B: printf("Type:B\t");break;
			default: printf("Type:Other\t");break;
		}
		printf("Number:%4d\n", decoder->pCodecParserCtx->output_picture_number);
        frameToYuv(packet_buf, packet_size);
	}

	return ret;
}
#endif

ErrorCode initDecoder(int fileSize, int logLv) {
	ErrorCode ret = kErrorCode_FFmpeg_Error;
	if (decoder != NULL) {
		ret = kErrorCode_Invalid_State;
	}
	decoder = (WebDecoder *)av_mallocz(sizeof(WebDecoder));
	if(decoder) {
		memset(decoder, 0, sizeof(WebDecoder));
		decoder->codec_id = AV_CODEC_ID_H264;
       	if (fileSize > 0) {
            decoder->fileSize = fileSize;
            sprintf(decoder->fileName, "tmp-%lu.mp4", getTickCount());
            decoder->fp = fopen(decoder->fileName, "wb+");
            if (decoder->fp == NULL) {
                // simpleLog("Open file %s failed, err: %d.", decoder->fileName, errno);
                ret = kErrorCode_Open_File_Error;
                av_free(decoder);
                decoder = NULL;
			}
        }

		ret = kErrorCode_Success;
	}
	return ret;
}

ErrorCode uninitDecoder() { 
    if (decoder != NULL) {
        if (decoder->fp != NULL) {
            fclose(decoder->fp);
            decoder->fp = NULL;
            remove(decoder->fileName);
        }

        // if (decoder->fifo != NULL) {
        //      av_fifo_freep(&decoder->fifo);
        // }
        av_freep(&decoder);
		decoder = NULL;
    }
	return kErrorCode_Success;
}

ErrorCode closeDecoder() {
#if ENABLE_PARSE      
	if(decoder->pCodecParserCtx) {
		av_parser_close(decoder->pCodecParserCtx);
	} 
#endif    
	if(decoder->pFrame) {
		av_frame_free(&decoder->pFrame);
	}
	if(decoder->pCodecCtx) {
		avcodec_close(decoder->pCodecCtx);
		av_free(decoder->pCodecCtx);
	}
	if (decoder->yuvBuffer != NULL) {
		av_freep(&decoder->yuvBuffer);
	}	
}

ErrorCode decodeOnePacket() {
    ErrorCode ret	= kErrorCode_Success;
	uint8_t out_buffer[CHUNKSIZE]={0};
	int readlen;
    do {
        if (decoder == NULL) {
            ret = kErrorCode_Invalid_State;
            break;
        }

        if (getAailableDataSize() <= 0) {
            ret = kErrorCode_Invalid_State;
            break;
        }

        readlen = readFromFile(out_buffer, sizeof(out_buffer));
        decodeBuf(out_buffer, readlen);
    } while (0);
    return ret;
}

ErrorCode openDecoder(int *paramArray, int paramCount, long videoCallback, long audioCallback, long requestCallback) {
	do {
		avcodec_register_all();
		decoder->pCodec = avcodec_find_decoder(decoder->codec_id);
		if (!decoder->pCodec) {
			printf("Codec not found\n");
			break;
		}
		decoder->pCodecCtx = avcodec_alloc_context3(decoder->pCodec);
		if (!decoder->pCodecCtx){
			printf("Could not allocate video codec context\n");
			break;
		}
#if ENABLE_PARSER  
		decoder->pCodecParserCtx = av_parser_init(decoder->codec_id);
		if (!decoder->pCodecParserCtx){
			printf("Could not allocate video parser context\n");
			break;
		}
#endif
		if (avcodec_open2(decoder->pCodecCtx, decoder->pCodec, NULL) < 0) {
			printf("Could not open codec\n");
			break;
		}

		decoder->pFrame = av_frame_alloc();
		if(!decoder->pFrame) {
			break;
		}

        decoder->videoCallback = (VideoCallback)videoCallback;
        // decodeOnePacket();
        // paramArray[0] = 0;
        // paramArray[1] = 0;
        // paramArray[2] = decoder->width;
        // paramArray[3] = decoder->height;        
		return kErrorCode_Success;
	} while(0);
	closeDecoder();
	return kErrorCode_FFmpeg_Error;
}


ErrorCode sendData(unsigned char *buff, int size) {
    ErrorCode ret = 0;
    do {
        if (decoder == NULL) {
            ret = -1;
            break;
        }

        if (buff == NULL || size == 0) {
            ret = -2;
            break;
        }

        ret = writeToFile(buff, size);
    } while (0);
    return ret;
}


ErrorCode seekTo(int ms, int accurateSeek) { 
    return 0;
}
void myVideoCallback(unsigned char *buff, int size, double timestamp) {
    // printf("%s|%d||size %d timestamp %f\n", __FUNCTION__, __LINE__, size, timestamp);
    if(fp_out) {
        fwrite(buff, size, 1, fp_out);
    }
}

int main(int argc, char* argv[])
{
#if 1
    if(argc !=2 ) {
        return 0;
    }
	char* filepath_in=argv[1];
	char filepath_out[]="out.yuv";
	
	uint8_t in_buffer[CHUNKSIZE]={0};
	int64_t filesize = get_file_size(filepath_in);
	
	initDecoder(filesize, CHUNKSIZE);
	
	
	//Input File
    fp_in = fopen(filepath_in, "rb");
    if (!fp_in) {
        printf("Could not open input stream\n");
        return -1;
    }

	fp_out = fopen(filepath_out, "wb");
	if (!fp_out) {
		printf("Could not open output YUV file\n");
		return -1;
	}


    
    int cur_len = fread(in_buffer, 1, 40356, fp_in);



    int paramCount = 7, paramSize = 4;
    char paramByteBuffer[28] = {0};
	openDecoder(paramByteBuffer, paramCount, myVideoCallback, NULL, NULL);

	//Output File
    decodeBuf(in_buffer, cur_len);
  
    // while(1) {
    //     if(decodeOnePacket() != kErrorCode_Success) {
    //         break;
    //     }
    // }

	fclose(fp_out);
 
	closeDecoder();
	uninitDecoder();
#endif    
	return 0;
}


#ifdef __cplusplus
}
#endif

